<?php
namespace App\Common\Lib\Cache\KeyDB\Db;

use App\Common as com;
use App\Common\Lib\Cache\KeyDB\Db\DbBase as nameDbBase;

class DbBase extends com\Cla\ClaBase\CommonOfId\Main {
	use com\Cla\ClaBase\Traits\GoPool;

	private $keyPathArray = [];

	private $param = [];

	private $_hyPackTag = '_hyPack:';

	private $_hyPackTagLenght = 8;

	private $dataCache = null;

	/**
	 * DbBase constructor.
	 * @param array|string $keyPath key路径数据。[{array}]多个路径
	 */
	public function __construct( $keyPath = [] ) {
		if( is_array( $keyPath ) ){
			$this->keyPathArray = $keyPath;
		}else{
			$this->keyPathArray[] = $keyPath;
		}
		//
		parent::__construct();
	}

	public function __get( string $cacheName ) {
		$this->keyPathArray[] = $cacheName;
		//
		return new static( $this->keyPathArray );
	}

	public function __call( $method, $args ) {
		$dbName = com\Lib\Fun\Array_::explodeGetValue( '\\', get_called_class(), -1 );
		// 生成类路径
		$classPath = 'App\\Common\\Cla\\Cache\\KeyDB\\Db\\' . $dbName . '\\' . implode( '\\', $this->keyPathArray );
		// 实例化类
		$classObj =  ( new $classPath( $this->keyPathArray ) );
		// 数据继承
		$this->thisClassDataInheritTo( $classObj );
		// 执行并返回
		return $classObj->$method( ...$args );
	}

	/**
	 * @return \Redis
	 */
	private function goRedis(  ) {
		return $this->goPool( 'goRedis', function(){
			return com\Lib\Hyperf\Container::getRedis();
		} );
	}

	/**
	 * 类数据继承
	 * @param DbBase $newClass 即将继承数据的类
	 * @return void
	 */
	private function thisClassDataInheritTo( DbBase $newClass ){
		$newClass->param( ...$this->param );
	}

	/**
	 * 附加key地址
	 * @param string $keyPath
	 * @return $this
	 */
	public function appendkeyPath( string $keyPath ):self {
		$this->keyPathArray[] = $keyPath;
		return $this;
	}

	/**
	 * 缓存键参数
	 * @param mixed ...$args
	 * @return $this
	 */
	public function param( ...$args ):self {
		$this->param = $args;
		return $this;
	}

	/**
	 * @return DbBase\_Cache
	 */
	public function goCache(  ):nameDbBase\_Cache {
		return $this->goPool( __FUNCTION__, function(){
			return nameDbBase\_Cache::make( $this->dataStore );
		} );
	}

	/**
	 * 清空缓存
	 * @return $this
	 */
	public function clearCache(  ):self {
		if( $this->goPoolIsset( 'goCache' ) ){
			$this->goCache()->clearCache();
		}
		//
		return $this;
	}

	/**
	 * 获取数据
	 * @param mixed $default 默认值。[false][默认]
	 * @return mixed 数据
	 * @throws
	 */
	public function get( $default = false ) {
		$value =  $this->goRedis()->get( $this->getKeyName() );
		return $value !== false ? $this->unpack( $value ) : $default;
	}

	/**
	 * 读取
	 * @param string $keyDbPath 数组路径（支持格式：1.PHP数组访问的格式，如：['a'][0]['b']， 2."->"分割，如：a->b->c）。
	 * @param mixed $default 默认值。[false][默认]
	 * @return mixed session数据
	 */
	public function getOfPath( string $keyDbPath, $default = false ) {
		$keyDbData = $this->get();
		if( $keyDbData === false ){
			return $default;
		}
		//
		if( $keyDbPath !== null ){
			// 检查数据是否存在
			if( ! com\Lib\Fun\Array_::assignPathIsset( $keyDbData, $keyDbPath ) ){
				return $default;
			}
			//
			return com\Lib\Fun\Array_::assignPathRead( $keyDbData, $keyDbPath );
		}else{
			return $keyDbData;
		}
	}

	/**
	 * 设置数据
	 * @param mixed $value 存储数据
	 * @param int|null $expire 有效时间，单位：秒。[null]永久 [int]必须大于0
	 * @return void
	 * @throws
	 */
	public function set( $value, ?int $expire ) {
		// ...
		if( $expire === 0 ){
			throw com\Lib\Err\Exception::makeOfNew( 'expire参数不能为0', com\Lib\Err\Code::$sysFunParamError );
		}
		//
		if( $this->goRedis()->setex( $this->getKeyName(), $expire, $this->pack( $value ) ) !== true ){
			throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysSqlSavaDataError );
		}
		//
		$this->clearCache();
	}

	/**
	 * 设置
	 * @param string $keyDbPath 数据路径（支持格式：1.PHP数组访问的格式，如：['a'][0]['b']， 2."->"分割，如：a->b->c）。
	 * @param mixed $value 数据
	 * @param int $expire 有效时间。[null][默认]跟随原数据剩余时间
	 * @return void
	 */
	public function setOfPath( string $keyDbPath, $value, int $expire = null ) {
		$keyDbData = $this->get( [] );
		com\Lib\Fun\Array_::assignPathWrite( $keyDbData, $keyDbPath, $value );
		// 写入
		$this->set( $keyDbData, $expire ?? $this->ttlOrNull() );
	}

	/**
	 * 设置
	 * @param array $keyDbPathArray 写入数据
	 * 如：[
	 * 		'a' => 1,
	 * 		'b' => [
	 * 			'c' => 2,
	 * 		],
	 * ]
	 * @param int $expire 有效时间。[null][默认]跟随原数据剩余时间
	 * @return void
	 */
	public function setOfArray( array $keyDbPathArray, int $expire = null ) {
		$keyDbData = $this->get( [] );
		// 合并
		$keyDbData = com\Lib\Fun\Array_::arrayMerges( $keyDbData, $keyDbPathArray );
		// 写入
		$this->set( $keyDbData, $expire ?? $this->ttlOrNull() );
	}

	/**
	 * 判断缓存是否存在
	 * @return boolean 是否存在 [true]存在 [false]不存在
	 */
	public function has(  ) {
		return $this->goRedis()->exists( $this->getKeyName() );
	}

	/**
	 * 获取过期时间（根据ttl）
	 * @return int|null 剩余过期时间。[null]永久。key 存在但没有设置剩余生存时间 [>0]剩余过期时间
	 * @throws
	 */
	public function ttlOrNull():?int {
		$ttl = $this->ttl();
		if( $ttl > 0 ){
			return $ttl;
		}else if( -1 == $ttl ){
			return null;
		}else if( -2 == $ttl ){
			throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataNotExist );
		}else{
			throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataError );
		}
	}

	/**
	 * 获取过期时间戳
	 * @return int 剩余过期时间。[null]永久。key 存在但没有设置剩余生存时间 [>0]剩余过期时间
	 * @throws
	 */
	public function ttlToTimestamp() {
		$ttl = $this->ttl();
		if( $ttl > 0 ){
			return $ttl;
		}else if( -1 == $ttl ){
			return null;
		}else if( -2 == $ttl ){
			throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataNotExist );
		}else{
			throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataError );
		}
	}

	/**
	 * 设置ttl
	 * @param int|null $expire 有效时间。[null | 0]永久
	 */
	public function setTtl( $expire ) {
		$cacheData = $this->get();
		if( false === $cacheData ) {
			throw com\Lib\Err\Exception::makeOfNew( '', com\Lib\Err\Code::$sysDataNotExist );
		}
		$this->set( $cacheData, $expire );
	}

	/**
	 * 命令以秒为单位返回 key 的剩余过期时间。
	 * @param string $name 缓存标识
	 * @return int 剩余过期时间。[-2]key不存在时 [-1]key 存在但没有设置剩余生存时间 [>=0]剩余过期时间
	 * @throws
	 */
	public function ttl() {
		return $this->goRedis()->ttl( $this->getKeyName() );
	}

	/**
	 * 自增缓存（针对数值缓存）
	 * @param  int    $step 步长
	 * @return int 修改后的值
	 */
	public function incX( $step = 1 ){
		return $this->goRedis()->incrby( $this->getKeyName(), $step );
	}

	/**
	 * 自减缓存（针对数值缓存）
	 * @param  int    $step 步长
	 * @return int 修改后的值
	 */
	public function decX( $step = 1 ){
		return $this->goRedis()->decrBy( $this->getKeyName(), $step );
	}

	/**
	 * 删除缓存
	 * @return int 删除数量
	 */
	public function del(  ):int {
		return $this->goRedis()->del( $this->getKeyName() );
	}

	/**
	 * 删除缓存(路径)
	 * @param string $keyDbPath 数据路径（支持格式：1.PHP数组访问的格式，如：['a'][0]['b']， 2."->"分割，如：a->b->c）
	 * @return void
	 */
	public function delOfPath( string $keyDbPath ) {
		$keyDbData = $this->get();
		com\Lib\Fun\Array_::assignPathUnset( $keyDbData, $keyDbPath );
		$this->set( $keyDbData, $this->ttlOrNull() );
	}

	/**
	 * 获取KeyName
	 * @return string KeyName
	 */
	public function getKeyName() {
		$keyName = implode( '.', $this->keyPathArray);
		if( ! empty( $this->param ) ){
			$keyName .= '->' . implode( '.', $this->param );
		}
		// d($keyName);
		return $keyName;
	}

	/**
	 * 获取缓存有效时间
	 * @param int $defaultExpire 默认值。[]
	 * @return int 缓存有效时间(秒)
	 */
	/*
	public function getExpire( $defaultExpire = 60 ) {
		return $defaultExpire;
	}
	*/

	/**
	 * 序列化
	 * @param $data
	 * @return string
	 */
	private function pack( $data ):string {
		if( is_array( $data ) || is_null( $data ) ){
			return $this->_hyPackTag . serialize( $data );
		}else{
			return $data;
		}
	}

	/**
	 * 反序列化
	 * @param string $data
	 * @return mixed
	 */
	private function unpack( string $data ) {
		if( substr( $data, 0, $this->_hyPackTagLenght ) === $this->_hyPackTag ){
			return unserialize( substr( $data, $this->_hyPackTagLenght ) );
		}else{
			return $data;
		}
	}

}
